Protected Screens, Role-Based Access, and Authentication Error Handling in React Native
Course: React Native Training
Topic: Protected Screens, Role-Based Access, and Authentication Error Handling
Course Link: React Native Training at JustAcademy
Demo Registration Link: Register for a Free Course Demo
JustAcademy Website: Visit JustAcademy
1. Introduction
Modern mobile applications often contain screens that should only be accessible to authenticated users or users with specific permissions. For example, a shopping application may allow everyone to browse products, but only signed-in users can view their orders. An administrator may have access to a dashboard that regular customers cannot open.
React Native applications can manage these requirements by combining protected screens, role-based access control, navigation logic, and reliable authentication error handling. These mechanisms help provide a consistent user experience while reducing the risk of exposing sensitive information.
Protected screens restrict access based on authentication or another defined condition. Role-based access control (RBAC) determines what a user is allowed to do based on their role or permissions. Authentication error handling manages situations such as invalid credentials, expired sessions, network failures, and rejected authentication requests.
1.1 Why These Concepts Matter
- Prevent signed-out users from opening account-specific screens.
- Separate administrator, staff, and customer functionality.
- Handle expired tokens and invalid sessions gracefully.
- Provide clear and useful error messages.
- Prevent navigation loops and confusing screen transitions.
- Improve application security and maintainability.
- Create a consistent authentication experience across screens.
1.2 Example Application
Consider a task-management application with three types of users:
| User Type |
Accessible Screens |
Example Permissions |
| Guest |
Welcome, Login, Register |
View public information and sign in. |
| Customer |
Home, Profile, My Tasks |
Manage personal profile and assigned tasks. |
| Manager |
Home, Profile, Team Dashboard |
View team progress and manage assigned team tasks. |
| Administrator |
Home, Profile, Admin Dashboard, User Management |
Manage users and perform administrative actions. |
The exact permissions depend on the application's requirements. A role should grant only the access needed to perform the user's responsibilities.
2. Learning Objectives
After completing this topic, learners should be able to:
- Understand protected screens and navigation guards.
- Distinguish authentication from authorization.
- Implement signed-in and signed-out navigation flows.
- Configure role-based screen access in React Native.
- Use React Context to share authentication state.
- Handle login, logout, session expiration, and permission errors.
- Prevent unauthorized access to sensitive operations.
- Display useful loading states and authentication messages.
- Test protected navigation and role-based access.
- Apply security best practices in production applications.
3. Authentication vs. Authorization
Authentication and authorization are related but solve different problems.
3.1 Authentication
Authentication answers the question: Who is the user? The application may authenticate a user through an email and password, an identity provider, a one-time password, or another supported mechanism.
After successful authentication, the server may return a session identifier, access token, refresh token, or another credential depending on the authentication architecture.
3.2 Authorization
Authorization answers the question: What is this user allowed to access or do? A signed-in user might be allowed to view their profile but not manage other users.
3.3 Comparison Table
| Aspect |
Authentication |
Authorization |
| Main question |
Who are you? |
What can you access? |
| Purpose |
Verify identity. |
Check permissions. |
| Example |
Signing in with valid credentials. |
Allowing an administrator to open User Management. |
| Typical failure |
Invalid credentials or expired session. |
Insufficient permissions or a forbidden operation. |
| React Native use |
Maintaining the current session. |
Controlling screens and actions based on permissions. |
Important: Hiding a screen in React Native does not secure the underlying data or API. The backend must independently verify authentication and authorization for every protected operation.
4. What Are Protected Screens?
A protected screen is a screen that should only be displayed when the user satisfies a required condition. Most commonly, the condition is that the user has a valid authenticated session. Other conditions may include account verification, subscription status, onboarding completion, or a required permission.
4.1 Public Screens
Public screens are available without signing in. Examples include:
- Welcome screen
- Login screen
- Registration screen
- Forgot Password screen
- Public product catalogue
- Help and support information
4.2 Protected Screens
Protected screens require a valid authenticated state or another explicit authorization condition. Examples include:
- Account Profile
- Order History
- Private Messages
- Personal Dashboard
- Saved Addresses
- Payment History
4.3 Role-Protected Screens
Role-protected screens require both an authenticated user and a suitable role or permission. Examples include an administrator dashboard, staff reports, or a manager's team-management screen.
5. Understanding the Authentication Flow
A typical authentication flow starts when the user opens the application. The application checks whether it has an existing session, displays the appropriate navigation stack, and responds to sign-in or sign-out events.
- The application starts and initializes authentication state.
- A loading indicator appears while the session is being checked.
- If no valid session exists, the application displays public authentication screens.
- The user submits their login credentials.
- The server validates the credentials.
- If authentication succeeds, the application stores session credentials using an appropriate secure mechanism and updates the authentication state.
- The navigation system displays authenticated screens.
- When the session expires or the user signs out, protected navigation is removed and the application returns to the public flow.
5.1 Navigation Flow Diagram
Application Starts
|
v
Initialize Authentication
|
v
Check Existing Session
|
+---- No valid session ----> Public Navigation
| |
| v
| Login Screen
| |
| v
| Submit Credentials
| |
| v
| Server Authentication
| |
| +--------+--------+
| | |
| Success Failure
| | |
| v v
| Authenticated Show Error
| Navigation
|
+---- Valid session ----> Authenticated Navigation
6. Setting Up Navigation in React Native
React Native applications commonly use React Navigation to manage navigation stacks, tabs, and nested navigators. The following example uses a stack navigator and assumes that the required navigation packages are already installed and configured.
6.1 Install Navigation Packages
For a project using React Navigation, install the core navigation package, the native stack package, and any required native dependencies according to the current installation guide.
npm install @react-navigation/native
npm install @react-navigation/native-stack
React Navigation projects may also require packages such as react-native-screens and react-native-safe-area-context. Follow the installation instructions for your React Native version and development environment.
6.2 Suggested Project Structure
src/
auth/
AuthContext.js
authService.js
navigation/
RootNavigator.js
AuthNavigator.js
AppNavigator.js
screens/
WelcomeScreen.js
LoginScreen.js
RegisterScreen.js
HomeScreen.js
ProfileScreen.js
AdminDashboardScreen.js
ForbiddenScreen.js
components/
LoadingScreen.js
ErrorMessage.js
utils/
permissions.js
App.js
Keeping authentication services, navigation, screens, and permission helpers separate makes the application easier to test and maintain.
7. Managing Authentication State with React Context
Authentication state is commonly needed by multiple screens and components. React Context provides a way to share that state without passing the same properties through every component.
An authentication context might contain:
user: the current user profile returned by the trusted backend.
isAuthenticated: whether the application currently considers the session authenticated.
isLoading: whether session initialization is still in progress.
signIn: a function that authenticates the user.
signOut: a function that ends the local session and, where supported, the server session.
7.1 Create AuthContext.js
import React, {
createContext,
useContext,
useEffect,
useMemo,
useState,
} from 'react';
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
let active = true;
async function initializeAuth() {
try {
// Replace with a real session-validation service.
// Do not trust an unvalidated user object from local storage.
const restoredUser = await restoreAndValidateSession();
if (active) {
setUser(restoredUser);
}
} catch (error) {
if (active) {
setUser(null);
}
} finally {
if (active) {
setIsLoading(false);
}
}
}
initializeAuth();
return () => {
active = false;
};
}, []);
async function signIn(credentials) {
const authenticatedUser = await authenticateWithServer(credentials);
setUser(authenticatedUser);
return authenticatedUser;
}
async function signOut() {
try {
await endServerSessionIfSupported();
} finally {
clearLocalSession();
setUser(null);
}
}
const value = useMemo(
() => ({
user,
isAuthenticated: Boolean(user),
isLoading,
signIn,
signOut,
}),
[user, isLoading]
);
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
}
// Illustrative placeholders: implement these in your auth service.
async function restoreAndValidateSession() {
return null;
}
async function authenticateWithServer(credentials) {
throw new Error('Connect this function to your authentication API.');
}
async function endServerSessionIfSupported() {
// Revoke the server session if the backend supports it.
}
function clearLocalSession() {
// Clear secure credentials and any session-related cache.
}
Implementation note: The functions in this example are placeholders. Replace them with real authentication-service methods. Do not treat a stored user object alone as proof that a session is still valid. The backend or trusted authentication provider should validate the session.
7.2 Wrap the Application with AuthProvider
import React from 'react';
import { AuthProvider } from './src/auth/AuthContext';
import RootNavigator from './src/navigation/RootNavigator';
export default function App() {
return (
<AuthProvider>
<RootNavigator />
</AuthProvider>
);
}
Once the provider wraps the navigation tree, any child component can read the shared authentication state by calling useAuth().
8. Building a Root Navigator for Protected Screens
A root navigator selects the correct group of screens based on authentication state. This is generally more reliable than manually navigating to a login screen from every protected screen.
8.1 Example RootNavigator.js
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { useAuth } from '../auth/AuthContext';
import LoadingScreen from '../components/LoadingScreen';
import AuthNavigator from './AuthNavigator';
import AppNavigator from './AppNavigator';
const Stack = createNativeStackNavigator();
export default function RootNavigator() {
const { isAuthenticated, isLoading } = useAuth();
if (isLoading) {
return <LoadingScreen />;
}
return (
<NavigationContainer>
<Stack.Navigator screenOptions={{ headerShown: false }}>
{isAuthenticated ? (
<Stack.Screen
name="App"
component={AppNavigator}
/>
) : (
<Stack.Screen
name="Auth"
component={AuthNavigator}
/>
)}
</Stack.Navigator>
</NavigationContainer>
);
}
In this pattern, the authenticated and unauthenticated navigation trees are mutually exclusive. When authentication state changes, React renders the corresponding tree. The user does not need to manually navigate through every screen to clear the old navigation history.
8.2 Why Loading State Is Important
When an application restores a session, it may take time to read secure storage or validate credentials. Rendering the login screen before initialization finishes can cause a brief flash of the wrong screen or unnecessary navigation changes.
Use a dedicated loading screen until the initial authentication check is complete.
import React from 'react';
import { ActivityIndicator, View } from 'react-native';
export default function LoadingScreen() {
return (
<View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
}}
>
<ActivityIndicator size="large" />
</View>
);
}
9. Creating Public and Protected Navigators
Separating public and protected screens makes access rules easier to understand.
9.1 Public Authentication Navigator
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import WelcomeScreen from '../screens/WelcomeScreen';
import LoginScreen from '../screens/LoginScreen';
import RegisterScreen from '../screens/RegisterScreen';
const Stack = createNativeStackNavigator();
export default function AuthNavigator() {
return (
<Stack.Navigator>
<Stack.Screen name="Welcome" component={WelcomeScreen} />
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="Register" component={RegisterScreen} />
</Stack.Navigator>
);
}
9.2 Protected Application Navigator
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from '../screens/HomeScreen';
import ProfileScreen from '../screens/ProfileScreen';
const Stack = createNativeStackNavigator();
export default function AppNavigator() {
return (
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
);
}
In a real project, add only the screens intended for authenticated users to the protected navigator. Add role-specific screens through conditional navigation or a dedicated authorization-aware navigator.
10. Implementing a Login Screen with Error Handling
A login screen collects credentials, submits them to an authentication service, and provides feedback when authentication fails. It should prevent duplicate submissions and avoid exposing sensitive information.
10.1 Example LoginScreen.js
import React, { useState } from 'react';
import {
ActivityIndicator,
Alert,
Button,
Text,
TextInput,
View,
} from 'react-native';
import { useAuth } from '../auth/AuthContext';
export default function LoginScreen() {
const { signIn } = useAuth();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
async function handleLogin() {
setErrorMessage('');
if (!email.trim() || !password) {
setErrorMessage('Enter your email and password.');
return;
}
try {
setIsSubmitting(true);
await signIn({
email: email.trim(),
password,
});
// The root navigator switches after auth state updates.
} catch (error) {
setErrorMessage(
error?.userMessage ||
'Unable to sign in. Please try again.'
);
} finally {
setIsSubmitting(false);
}
}
return (
<View style={{ flex: 1, padding: 24, justifyContent: 'center' }}>
<Text>Sign In</Text>
<TextInput
placeholder="Email address"
value={email}
onChangeText={setEmail}
autoCapitalize="none"
keyboardType="email-address"
autoComplete="email"
/>
<TextInput
placeholder="Password"
value={password}
onChangeText={setPassword}
secureTextEntry
autoComplete="current-password"
/>
{errorMessage ? (
<Text accessibilityRole="alert">{errorMessage}</Text>
) : null}
{isSubmitting ? (
<ActivityIndicator />
) : (
<Button title="Sign In" onPress={handleLogin} />
)}
</View>
);
}
This example assumes that the authentication service throws errors with optional user-safe messages. In a production application, map server errors to controlled messages rather than displaying arbitrary server responses directly.
11. Role-Based Access Control (RBAC)
Role-based access control assigns roles to users and uses those roles to determine which features and actions are permitted. Roles are usually defined by the application's business requirements and supplied or validated by a trusted backend.
11.1 Common Roles
- Guest: can access public features only.
- Customer: can manage their own profile, orders, or personal content.
- Employee: can access assigned work-related features.
- Manager: can access team reports and permitted team operations.
- Administrator: can perform approved administrative tasks.
11.2 Role and Permission Are Not the Same
A role is a named grouping of responsibilities. A permission is a specific allowed action, such as users:read, users:manage, or reports:view. Permission-based checks can be more flexible than checking role names everywhere.
| Role |
Example Permissions |
| Customer |
profile:read, profile:update, orders:read_own |
| Support Agent |
tickets:read, tickets:reply |
| Manager |
reports:view_team, tasks:assign |
| Administrator |
users:read, users:manage, settings:manage |
These permissions are illustrative. A real application should define a minimal and explicit permission model.
12. Implementing Role-Based Screen Access
Once the current user's role or permissions are available in trusted authentication state, the application can decide which screens to display. This improves navigation clarity, but it must be combined with backend authorization.
12.1 Example Role-Based Navigator
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from '../screens/HomeScreen';
import ProfileScreen from '../screens/ProfileScreen';
import AdminDashboardScreen from '../screens/AdminDashboardScreen';
import ForbiddenScreen from '../screens/ForbiddenScreen';
import { useAuth } from '../auth/AuthContext';
const Stack = createNativeStackNavigator();
export default function AppNavigator() {
const { user } = useAuth();
const role = user?.role;
return (
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
{role === 'admin' ? (
<Stack.Screen
name="AdminDashboard"
component={AdminDashboardScreen}
/>
) : null}
{role !== 'admin' ? (
<Stack.Screen
name="Forbidden"
component={ForbiddenScreen}
/>
) : null}
</Stack.Navigator>
);
}
This is a simplified example. In an actual application, do not add a forbidden screen to the navigator simply because the user is not an administrator unless that screen is intended to be reachable. More importantly, never rely on conditional screen registration as the only authorization check.
12.2 Alternative: Use a Permission Helper
export function hasPermission(user, requiredPermission) {
if (!user || !Array.isArray(user.permissions)) {
return false;
}
return user.permissions.includes(requiredPermission);
}
export function hasAnyPermission(user, requiredPermissions) {
return requiredPermissions.some((permission) =>
hasPermission(user, permission)
);
}
export function hasAllPermissions(user, requiredPermissions) {
return requiredPermissions.every((permission) =>
hasPermission(user, permission)
);
}
Using a centralized permission helper prevents inconsistent checks across screens. Missing user data or permissions should default to denying access.
13. Creating a Reusable Permission Guard
A reusable guard component can display a screen only when the current user has the required permission. It is useful for protecting individual sections or actions inside a screen.
13.1 PermissionGuard.js
import React from 'react';
import { Text, View } from 'react-native';
import { useAuth } from '../auth/AuthContext';
import { hasAllPermissions } from '../utils/permissions';
export default function PermissionGuard({
permissions = [],
children,
}) {
const { user } = useAuth();
const allowed = hasAllPermissions(user, permissions);
if (!allowed) {
return (
<View style={{ padding: 20 }}>
<Text>You do not have permission to view this content.</Text>
</View>
);
}
return children;
}
13.2 Using the Guard
import React from 'react';
import { Button, View } from 'react-native';
import PermissionGuard from '../components/PermissionGuard';
export default function ReportsScreen() {
return (
<View>
<PermissionGuard permissions={['reports:view_team']}>
<TeamReports />
</PermissionGuard>
<PermissionGuard permissions={['tasks:assign']}>
<Button
title="Assign Task"
onPress={() => {
// Call a protected API and handle its response.
}}
/>
</PermissionGuard>
</View>
);
}
function TeamReports() {
return null; // Replace with the actual report UI.
}
The guard improves the user interface, but the backend must still verify the permission when the report data or task assignment is requested.
14. Handling Authentication Errors
Authentication errors occur when the application cannot establish or maintain a valid session. Common causes include invalid credentials, expired tokens, revoked sessions, network interruptions, account restrictions, and server failures.
14.1 Common Error Categories
| Error |
Possible Cause |
Recommended Response |
| Invalid credentials |
Incorrect email or password. |
Show a generic sign-in failure message and allow retry. |
| Missing credentials |
Required input is empty. |
Validate input before sending the request. |
| Network failure |
Offline device, timeout, or connection issue. |
Show a connection message and allow retry. |
| Unauthorized response |
Session is missing, invalid, expired, or rejected. |
Refresh the session if supported; otherwise clear the session and ask the user to sign in again. |
| Forbidden response |
User lacks permission for an operation. |
Keep the session active and show an access-denied message. |
| Rate limit |
Too many requests or sign-in attempts. |
Respect retry guidance and avoid repeated automatic requests. |
| Server error |
Backend failure. |
Show a general temporary-service message. |
| Account verification required |
Email or identity verification is incomplete. |
Guide the user through the verification flow. |
14.2 User-Friendly Error Messages
Messages should explain the next step without exposing internal implementation details.
| Situation |
Suggested Message |
| Incorrect sign-in information |
We couldn't sign you in. Check your details and try again. |
| Connection problem |
Unable to connect. Check your internet connection and try again. |
| Session expired |
Your session has expired. Please sign in again. |
| Insufficient permissions |
You don't have permission to perform this action. |
| Temporary service issue |
The service is temporarily unavailable. Please try again later. |
| Verification required |
Please complete account verification to continue. |
Avoid showing raw stack traces, database errors, token values, internal URLs, or confidential server responses in the user interface. For login failures, avoid revealing whether a particular account exists unless the product's security design explicitly permits that information.
15. Creating a Centralized Authentication Error Handler
A centralized error handler maps technical failures to predictable application-level error codes and user-safe messages. This avoids repeating complex status-code checks throughout the application.
15.1 Example authErrors.js
export function normalizeAuthError(error) {
const status = error?.status;
const code = error?.code;
if (code === 'NETWORK_ERROR' || !status) {
return {
type: 'network',
userMessage:
'Unable to connect. Check your connection and try again.',
};
}
if (status === 400 || status === 401) {
return {
type: 'credentials',
userMessage:
'We could not sign you in. Check your details and try again.',
};
}
if (status === 403) {
return {
type: 'forbidden',
userMessage:
'You do not have permission to perform this action.',
};
}
if (status === 429) {
return {
type: 'rate_limit',
userMessage:
'Too many attempts. Please wait before trying again.',
};
}
if (status >= 500) {
return {
type: 'server',
userMessage:
'The service is temporarily unavailable. Try again later.',
};
}
return {
type: 'unknown',
userMessage:
'Something went wrong. Please try again.',
};
}
The mapping above is illustrative. Adapt it to the actual API contract: some services use different status codes for validation, expired credentials, verification requirements, and rate limiting. A missing status does not always mean a network failure, so a production error type should distinguish transport errors from unexpected application errors.
15.2 Use the Error Handler in Login
import { normalizeAuthError } from '../utils/authErrors';
async function handleLogin() {
setErrorMessage('');
try {
setIsSubmitting(true);
await signIn({ email: email.trim(), password });
} catch (error) {
const authError = normalizeAuthError(error);
setErrorMessage(authError.userMessage);
} finally {
setIsSubmitting(false);
}
}
16. Handling Expired Sessions and Unauthorized API Responses
Access tokens may expire, sessions may be revoked, and user permissions may change while an application is open. The application should respond to such events consistently rather than leaving protected screens in a broken state.
16.1 Recommended Flow
- A protected API request receives an authentication failure.
- If the authentication system supports refresh tokens, the client attempts the defined refresh process.
- If refresh succeeds, the original request may be retried once, subject to the application's API-client design.
- If refresh fails or the session is revoked, clear local session credentials and authentication-related cached data.
- Update authentication state so protected navigation is removed.
- Display a brief message explaining that the user needs to sign in again.
16.2 Avoid Infinite Retry Loops
Do not automatically retry a failed request indefinitely. Limit refresh attempts, prevent multiple simultaneous refresh operations from racing, and avoid retrying a request after the refresh process has already failed.
16.3 Session Expiration Example
async function handleUnauthorizedSession({ signOut, showMessage }) {
try {
await signOut();
} finally {
showMessage('Your session has expired. Please sign in again.');
}
}
In a larger application, session expiration is often coordinated in the shared API client or authentication service. Avoid implementing independent, competing logout logic in every screen.
17. Handling Forbidden Access (403)
An authentication failure and a permission failure should not be treated as identical. A user may have a valid session but lack permission to access a particular resource.
- Authentication failure: the server cannot accept the current credentials or session.
- Authorization failure: the server recognizes the identity but rejects the requested action because permission is insufficient.
For a permission failure, the application should generally keep the user signed in, display an access-denied message, and avoid repeatedly requesting the same forbidden resource. The backend must make the final decision based on the authenticated user and requested resource.
17.1 Forbidden Screen Example
import React from 'react';
import { Button, Text, View } from 'react-native';
export default function ForbiddenScreen({ navigation }) {
return (
<View style={{ flex: 1, padding: 24, justifyContent: 'center' }}>
<Text>Access Denied</Text>
<Text>
You do not have permission to view this page.
</Text>
<Button
title="Go Back"
onPress={() => navigation.goBack()}
/>
</View>
);
}
18. Handling Logout and Protected Navigation
Logout should clear the application's authenticated state and prevent the user from returning to private screens through the previous navigation history.
18.1 Logout Requirements
- Clear session credentials from secure storage.
- Revoke or invalidate the server session when supported.
- Clear sensitive user-specific caches and in-memory state.
- Update the authentication context.
- Remove authenticated navigation from the rendered navigation tree.
- Ensure background requests cannot repopulate data for the signed-out user.
18.2 Logout Button Example
import React from 'react';
import { Button } from 'react-native';
import { useAuth } from '../auth/AuthContext';
export default function LogoutButton() {
const { signOut } = useAuth();
async function handleLogout() {
try {
await signOut();
} catch (error) {
// Record a safe diagnostic event if appropriate.
// Local session cleanup should still occur.
}
}
return (
<Button
title="Log Out"
onPress={handleLogout}
/>
);
}
When authentication state becomes unauthenticated, the root navigator in the earlier example switches to the public navigator. This prevents the authenticated navigation stack from remaining active.
19. Securing API Requests
Protected screens frequently load private information from an API. Every request to a protected endpoint must be authenticated according to the backend's requirements.
19.1 Example Authenticated Request
async function fetchMyProfile(accessToken) {
const response = await fetch('https://api.example.com/me', {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${accessToken}`,
},
});
if (response.status === 401) {
throw {
status: 401,
code: 'UNAUTHORIZED',
};
}
if (response.status === 403) {
throw {
status: 403,
code: 'FORBIDDEN',
};
}
if (!response.ok) {
throw {
status: response.status,
code: 'API_ERROR',
};
}
return response.json();
}
The URL above is a placeholder. Replace it with your backend endpoint and follow the API's documented authentication format. Keep access tokens out of logs and error messages. The backend must verify the token, enforce resource ownership, and validate the requested action on every protected request.
19.2 Enforce Resource Ownership
It is not enough to check whether a user is signed in. For example, an order endpoint must verify that the requested order belongs to the authenticated user or that the user has a specific administrative permission. Never rely on a client-supplied user ID alone to decide ownership.
20. Secure Session Storage and Role Data
Protected navigation depends on trustworthy authentication state. Sensitive credentials should be stored using platform-appropriate secure storage rather than ordinary unencrypted application storage.
- Use an established secure-storage solution appropriate to the React Native environment.
- Never hard-code access tokens, refresh tokens, passwords, or private keys in source code.
- Do not treat a locally stored role as authoritative for sensitive operations.
- Refresh user permissions when the backend requires it or when the session changes.
- Clear sensitive caches on logout and account switching.
- Use HTTPS for authentication and protected API requests.
- Do not store passwords for later automatic login.
Local role data can help decide what to render, but the server must remain the source of truth for permissions. If the application supports account suspension or role changes, make sure stale client state cannot continue authorizing sensitive operations.
21. Handling Multiple Roles and Permissions
Some users may have more than one role. For example, a staff member may also be a report viewer. In such cases, checking only one role can produce incorrect access decisions.
21.1 User with Multiple Roles
const user = {
id: 'user-123',
roles: ['support', 'report_viewer'],
permissions: ['tickets:read', 'tickets:reply', 'reports:view'],
};
21.2 Check Whether a User Has a Role
export function hasRole(user, requiredRole) {
return Array.isArray(user?.roles) &&
user.roles.includes(requiredRole);
}
const canViewReports = hasRole(user, 'report_viewer');
21.3 Check Whether a User Has One of Several Roles
export function hasAnyRole(user, allowedRoles) {
if (!Array.isArray(user?.roles)) {
return false;
}
return allowedRoles.some((role) =>
user.roles.includes(role)
);
}
const canAccessTeamTools = hasAnyRole(user, [
'manager',
'admin',
]);
Use role checks when the business rule is explicitly role-based. Use permission checks when the action needs to be controlled independently of the role name. Document how multiple roles combine and whether an action requires any or all listed permissions.
22. Authentication Error Handling with Async Operations
Authentication involves asynchronous operations such as network requests, secure-storage reads, token refresh, and server logout. Proper handling prevents stuck loading indicators, unhandled promise rejections, and inconsistent application state.
22.1 Use try, catch, and finally
async function performAuthenticatedAction() {
setIsLoading(true);
setErrorMessage('');
try {
const result = await runProtectedOperation();
return result;
} catch (error) {
setErrorMessage(
'The requested action could not be completed.'
);
throw error;
} finally {
setIsLoading(false);
}
}
The finally block runs whether the operation succeeds or fails, so it is a reliable place to reset a loading state. If an error is rethrown, the calling function must handle it appropriately to avoid an unhandled rejection.
22.2 Prevent Duplicate Submissions
Disable the submit button while authentication is in progress. This reduces accidental duplicate requests and helps prevent confusing user experiences. Server-side rate limiting and abuse prevention are still required; disabling a button is not a security control.
23. Accessibility and User Experience
Authentication flows should be accessible, predictable, and understandable.
- Label email and password fields clearly.
- Make validation and authentication errors readable by assistive technologies.
- Do not communicate errors through color alone.
- Show loading indicators while requests are in progress.
- Keep the user informed when their session expires.
- Provide a clear route back to a permitted screen after an access-denied response.
- Preserve form input where appropriate without retaining sensitive passwords unnecessarily.
- Ensure the application handles keyboard navigation and screen readers appropriately.
24. Common Mistakes to Avoid
| Mistake |
Why It Is a Problem |
Better Approach |
| Checking only whether a token string exists |
The token may be expired, invalid, or revoked. |
Validate or refresh the session according to the authentication design. |
| Trusting a client-side role as the only access check |
Client state can be modified or become stale. |
Enforce permissions on the backend for every protected request. |
| Showing private screens before session initialization |
Users may see a brief incorrect screen or sensitive cached content. |
Wait for the initial authentication check to finish. |
| Sending every error to the login screen |
A forbidden request does not necessarily mean the session is invalid. |
Handle authentication and authorization errors separately. |
| Displaying raw server errors |
May expose internal details or sensitive information. |
Map errors to safe and useful messages. |
| Keeping private data after logout |
A later user of the device may see previous account information. |
Clear sensitive caches and user-specific state. |
| Retrying failed requests indefinitely |
Can cause loops, duplicate operations, or excessive traffic. |
Use bounded retries and a coordinated refresh process. |
| Only hiding administrative buttons |
Users may still call the underlying endpoint directly. |
Validate permissions on the server. |
25. Testing Protected Screens and Role-Based Access
Testing should cover both expected flows and failure cases. Verify navigation, UI state, API responses, session expiration, and permissions.
25.1 Authentication Test Cases
| Test Case |
Expected Result |
| Open app with no session |
Public navigation appears after initialization. |
| Open app with a valid session |
Authenticated navigation appears. |
| Submit invalid credentials |
A safe error message appears and the app remains signed out. |
| Submit valid credentials |
The authenticated navigation tree appears. |
| Session expires during use |
Refresh is attempted if supported; otherwise the session is cleared and sign-in is required. |
| Tap logout |
Local credentials and private state are cleared and public navigation appears. |
| Network fails during login |
A connection error appears and the user can retry. |
25.2 Authorization Test Cases
| Test Case |
Expected Result |
| Customer opens an admin screen |
Access is denied by the client UI and by the backend endpoint. |
| Manager opens a permitted team report |
The report appears if the required permission is valid. |
| User has no permission data |
Protected actions are denied by default. |
| Role is revoked while the app is open |
Updated permissions are enforced on subsequent protected requests. |
| User requests another customer's data |
The backend rejects access unless explicitly authorized. |
| Forbidden API response occurs |
The app shows an access-denied message without unnecessarily signing the user out. |
25.3 Manual Testing Checklist
- Verify all public screens are available without signing in.
- Verify protected screens are not accessible after logout.
- Verify each role sees only the intended navigation options.
- Verify permissions are checked when protected actions are performed.
- Test invalid credentials, expired sessions, and network errors.
- Test slow responses and duplicate taps on login.
- Confirm loading indicators always stop when a request finishes.
- Confirm private data does not remain visible after account switching.
- Confirm raw tokens and sensitive details never appear in logs or error messages.
26. Mini Project: Role-Based Task Management App
Build a React Native task-management application with public screens, protected navigation, multiple roles, and authentication error handling.
26.1 Project Requirements
- Welcome screen for unauthenticated users.
- Login and registration screens.
- Home and profile screens for authenticated users.
- Manager dashboard for users with the manager role.
- Admin dashboard for users with administrative permission.
- Reusable permission helper and guard component.
- Centralized authentication error mapping.
- Session restoration and logout.
- Clear loading, success, and error states.
- Backend authorization for protected API requests.
26.2 Suggested Development Steps
- Create the React Native project and install navigation dependencies.
- Define the user model, role names, and permission rules.
- Create an authentication service for login, logout, and session validation.
- Create an authentication context and initial loading state.
- Build the public and authenticated navigators.
- Add conditional navigation or permission-aware screens.
- Create reusable role and permission helper functions.
- Map API errors to safe messages and consistent error types.
- Handle session expiration and clear private state on logout.
- Test the application with guest, customer, manager, and administrator accounts.
- Verify that the backend rejects unauthorized access even when a request is made outside the intended UI.
26.3 Expected Outcome
The completed project should direct users to the appropriate navigation flow, restrict role-specific features, handle common authentication failures, and rely on backend checks to protect sensitive information and operations.
27. Interview Questions and Answers
Q1. What is a protected screen in React Native?
A protected screen is a screen that is displayed only when a user meets defined requirements, such as having a valid authenticated session or a required permission.
Q2. What is the difference between authentication and authorization?
Authentication verifies the user's identity. Authorization determines whether that identity has permission to access a resource or perform an action.
Q3. What is role-based access control?
RBAC groups permissions under roles and assigns roles to users. The application uses those roles to determine which features and actions are permitted.
Q4. Why should the app use a loading state during authentication initialization?
It prevents the application from displaying the wrong navigation tree before the existing session has been checked.
Q5. Why is React Context useful for authentication?
It shares authentication state and related actions across the component tree without requiring every intermediate component to pass the same properties.
Q6. Should role checks only happen in React Native?
No. Client-side checks improve the interface, but the backend must enforce authentication, permissions, and resource ownership on protected requests.
Q7. What is the difference between HTTP 401 and 403?
In common API conventions, 401 indicates that valid authentication is missing or not accepted, while 403 indicates that the request is understood but access is not permitted. Follow the actual API contract because implementations can vary.
Q8. What should happen when an access token expires?
If supported, the app can attempt a controlled token refresh. If refresh fails or the session is revoked, it should clear the session, remove protected navigation, and request sign-in again.
Q9. How should authentication errors be displayed?
Display clear, safe messages that explain what the user can do next. Do not expose raw server responses, stack traces, tokens, or other confidential details.
Q10. Why is a centralized error handler useful?
It creates consistent error categories and messages, reduces duplicated code, and makes the application easier to maintain and test.
Q11. How do you prevent users from returning to protected screens after logout?
Clear authentication state and render the public navigation tree instead of leaving the protected stack active. Also clear sensitive cached data and invalidate the server session where supported.
Q12. Can a user have multiple roles?
Yes. The application can support several roles or permissions for a single user. The authorization rules should define whether an action requires any or all of the specified permissions.
Q13. What is the purpose of a permission guard?
A permission guard conditionally renders a component or section when the user has the required permission. It is a UI control and does not replace backend authorization.
Q14. What should happen when a user receives a 403 response?
The application should normally show an access-denied message and keep the session active unless another authentication or account policy requires a different response.
Q15. How do you test role-based access?
Test every role against permitted and forbidden screens and API actions, including missing permissions, revoked roles, direct API requests, and access to another user's data.
28. Quick Revision Notes
- Protected screen: a screen restricted by authentication or another access condition.
- Authentication: verifies identity.
- Authorization: checks allowed actions and resources.
- RBAC: assigns permissions through roles.
- Permission: an explicit allowed action, such as viewing a report.
- Auth Context: shares session state and authentication actions across components.
- Root Navigator: selects the public or authenticated navigation tree.
- Loading state: prevents premature navigation while session initialization is in progress.
- 401: commonly indicates an authentication problem.
- 403: commonly indicates insufficient authorization.
- Error normalization: converts technical errors into consistent application errors.
- Secure storage: protects sensitive session credentials using platform-appropriate storage.
- Backend enforcement: protects data and actions even when client-side checks are bypassed.
- Logout: clears local session state, private caches, and the server session where supported.
29. Best Practices Checklist
- Use a single, well-defined authentication state source.
- Separate public screens from authenticated screens.
- Wait for session initialization before rendering the main navigation flow.
- Use secure storage for sensitive credentials.
- Keep roles and permissions explicit and documented.
- Default to denying access when required permission information is missing.
- Validate authentication and permissions on the backend.
- Enforce resource ownership on every relevant API request.
- Distinguish authentication failures from authorization failures.
- Centralize error normalization and user-facing messages.
- Limit refresh and retry attempts.
- Clear sensitive state when the user logs out or changes accounts.
- Test both allowed and denied paths for every role.
- Never log passwords, access tokens, refresh tokens, or confidential responses.
30. Conclusion
Protected screens, role-based access, and authentication error handling are essential parts of a reliable React Native application. Protected navigation helps keep the interface appropriate to the user's session, RBAC organizes access around roles and permissions, and consistent error handling guides users through invalid credentials, expired sessions, network problems, and forbidden actions.
A strong implementation combines React Context or another consistent state-management approach, separate public and authenticated navigators, reusable permission helpers, secure credential handling, and clear error messages. Most importantly, client-side navigation and permission checks must be backed by server-side authorization. This combination creates a more secure, maintainable, and user-friendly mobile application.
31. Learning Resources